A lesson of @Transactional


Recently, I needed to work with a Cursor for statistics.

<mapper ...>
  <select id="selectCorsor" resultOrdered="true">
  </select>
</mapper>

It didn’t make any difference.

@Mapper
public interface SomeMapper {
    Cursor<Some> selectCursor(...);
}

We need the @Transactional annotation for working with Cursor.

@Service
public class SomeService {

    @Transactional
    public <R> R applyCursor(
            ...,
            final Function<Cursor<Some>, R> function) {
         return function.apply(someMapper.selectCursor(..));
    }

    @Autowired
    private SomeMapper someMapper;
}

Well the method worked as expected.

The problem arose when I added a method using the origin method.

    public void acceptEach(
            ...,
            final Consumer<Some> consumer) {
        applyCursor(
            ...,
            cursor -> {
                cursor.forEach(consumer::accept);
                return null;
            }
        );
    }

This auxiliary method was not annotated with @Transactional and it didn’t work.

And I found @Transactional method calling another method without @Transactional anotation?.

The acceptEach method was also required to be annotated with @Transactional​.

Using Cursors with MyBatis


There are queries that you shouldn’t map as List<T>.

And the Cursor comes to play.

@Service
public class SomeService {

    public Cursor<Some> getCursor(...) {
        // returns the cursor?
    }
}

Well it not a good way to do with it. The return value Cursor<Some> won’t work because the session may already be finished when the method returns.

@Service
public class SomeService {

    @Transactional
    public <R> R applyCursor(
            ...,
            Function<Cursor<Some>, R> function) {
        final Cursor<Some> cursor = ...;
        return function.apply(cursor);
    }
}

Now we can add some other methods to do with it.

    public <R> R applyIterator(
            ...,
            Function<Iterator<Some>, R> function) {
        applyCursor(
                ...,
                cursor -> function.apply(cursor.iterator())
        );
    }

What about a Spliterator?

    public <R> R applySpliterator(
            ...,
            Function<Spliterator<Some>, R> function) {
        applyIterator(
                ...,
                iterator -> {
                    int characteristics
                        = Spliterator.DISTINCT
                          | Spliterator.IMMUTABLE
                          | Spliterator.NONNULL
                          | Spliterator.ORDERED;
                    Spliterator<CsEventAssociate> spliterator
                        = Spliterators.spliteratorUnknownSize(
                                iterator, characteristics);
                    return function.apply(spliterator);
                }
        );
    }

And the Stream?

    public <R> R applyStream(
            ...,
            Function<Stream<Some>, R> function) {
        return applySpliterator(
            ...,
            spliterator -> function.apply(StreamSupport.stream(spliterator, false))
        );
    }

Getting a Substring in MySQL


I just learned how to get substring of specific range using LOCATE() and SUBSTRING().

Let’s say we have email addresses and want to get username part from them.

Step 1 Locate the index of ‘@’ character

LOCATE('@', email)

Step 2 Get substring using it.

SUBSTRING(email, 1, LOCATE('@', email) - 1)

Note that the starting index is 1 and the the len part must be 1 minus the location.

Wait.

There is a function exactly does this. SUBSTRING_INDEX().

SUBSTRING_INDEX(email, '@', 1)

Injecting Property Values into EJBs from Database


references

Property.java

@Entity
@NamedQueries({
@NamedQuery(name = "Property.find",
            query = "SELECT p FROM Property AS p WHERE p.name = :name")
})
public class Property {

    @Basic(optional = false)
    @Column(name = "NAME", nullable = false, unique = true)
    @NotNull
    private String name;

    @Basic(Optional = false)
    @Column(name = "VALUE_", nullable = false)
    @NotNull
    private String value = "";
}

find

public static Property findNamed(final EntityManager entityManager,
                                 final String name) {
    final TypedQuery typedQuery
        = entityManager.createNamedQuery("Property.find", Property.class);
    typedQuery.setParameter("name", name);
    try {
        return typedQuery.getSingleResult();
    } catch (final NoResultException nre) {
        return null;
    }
}

public static Property findCriteria(final EntityManager entityManager,
                                    final String name) {
    final CriteriaBuilder criteriaBuilder
        = entityManager.getCriteriaBuilder();
    final CriteriaQuery criteriaQuery
        = criteriaBuilder.createQuery(Property.class);
    final Root<Property> property = criteriaQuery.from(Property.class);
    criteriaQuery.select(property);
    criteriaQuery.where(
        criteriaBuilder.equal(property.get(Property_.name), name));
    final TypedQuery typedQuery
        = entityManager.createQuery(criteriaQuery);
    try {
        return typedQuery.getSingleResult();
    } catch (final NoResultException nre) {
        return null;
    }
}

public static Property find(final EntityManager entityManager,
                            final String name) {
    if (current().nextBoolean()) {
        return findNamed(entityManager, name);
    }
    return findCriteria(entityManager, name);
}

PropertyValue.java

@Qualifier
@Retention(RUNTIME)
@Target({FIELD, METHOD, PARAMETER, TYPE})
public @interface PropertyValue {

    @Nonbinding
    String name() default "";

    @Nonbinding
    boolean optional() default true;
}

PropertyFactory.java

@Dependent
public class PropertyValueFactory {

@Produces
@PropertyValue
public String producePropertyValue(final InjectionPoint injectionPoint) {
    final PropertyValue annotation
        = injectionPoint.getAnnotated().getAnnotation(PropertyValue.class);
    final String name = annotation.name();
    final boolean optional = annotation.optional();
    final Property property = Property.find(entityManger, name);
    if (property == null && !optional) {
        throw new InjectionException("property not found; name= " + name);
    }
    return ofNullable(property).map(Property::getValue).orElse(null);
}

public void diposePropertyValue(
    @Disposes @PropertyValue final String value) {
    // empty


@PersistenceContext
private EntityManager entityManger;
}

StorageObjectServiceFtp.java

 @Stateless
 public class StorageObjectServiceFtp extends StorageObjectService {

    @Inject
    @PropertyValue(name = "ftp.hostname", optional = false)
    private String hostname;

    @Inject
    @PropertyValue(name = "ftp.username", optional = false)
    private String username;

    @Inject
    @PropertyValue(name = "ftp.password", optional = false)
    private String password;

    @Inject
    @PropertyValue(name = "ftp.rootpath", optional = true)
    private String rootpath;
}

Basic Java EE with Apache Maven


some-parent

다른 모듈들에서 사용할 dependency/plugin 등을 정리하는 모듈이다. java-ee-apiderby, h2 등의 메모리 DB, 그리고, eclipselink, hibernate-core, openejb-core 등이 정의되었다.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>some.group</groupId>
  <artifactId>some-parent</artifactId>
  <version>0.1.0-SNAPSHOT</version>
  <packaging>pom</packaging>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
    <version.org.apache.tomee>7.0.2</version.org.apache.tomee>
    <version.org.eclipse.persistence>2.6.4</version.org.eclipse.persistence>
    <version.org.glassfish.jersey>2.25</version.org.glassfish.jersey>
    <version.org.glassfish.jersey.security>${version.org.glassfish.jersey}</version.org.glassfish.jersey.security>
    <version.org.glassfish.jersey.test-framework>${version.org.glassfish.jersey}</version.org.glassfish.jersey.test-framework>
    <version.org.wildfly.swarm>2017.1.1</version.org.wildfly.swarm>
  </properties>

  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-classic</artifactId>
        <version>1.1.8</version>
      </dependency>
      <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
        <version>1.4.193</version>
      </dependency>
      <dependency>
        <groupId>javax</groupId>
        <artifactId>javaee-api</artifactId>
        <version>7.0</version>
      </dependency>
      <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-lang3</artifactId>
        <version>3.5</version>
      </dependency>
      <dependency>
        <groupId>org.apache.derby</groupId>
        <artifactId>derby</artifactId>
        <version>10.13.1.1</version>
      </dependency>
      <dependency>
        <groupId>org.apache.tomee</groupId>
        <artifactId>openejb-core</artifactId>
        <version>${version.org.apache.tomee}</version>
      </dependency>
      <dependency>
        <groupId>org.eclipse.persistence</groupId>
        <artifactId>eclipselink</artifactId>
        <version>${version.org.eclipse.persistence}</version>
      </dependency>
      <dependency>
        <groupId>org.gavaghan</groupId>
        <artifactId>geodesy</artifactId>
        <version>1.1.3</version>
      </dependency>
      <dependency>
        <groupId>org.glassfish</groupId>
        <artifactId>javax.el</artifactId>
        <version>3.0.1-b08</version>
      </dependency>
      <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-core</artifactId>
        <version>5.2.6.Final</version>
      </dependency>
      <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-validator</artifactId>
        <version>5.4.0.CR1</version>
      </dependency>
      <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>1.7.22</version>
      </dependency>
      <dependency>
        <groupId>org.testng</groupId>
        <artifactId>testng</artifactId>
        <version>6.10</version>
      </dependency>
    </dependencies>
  </dependencyManagement>

  <build>
    <pluginManagement>
      <plugins>
        <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-failsafe-plugin</artifactId>
          <version>2.19.1</version>
        </plugin>
        <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-javadoc-plugin</artifactId>
          <version>2.10.4</version>
        </plugin>
        <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-pmd-plugin</artifactId>
          <version>3.7</version>
        </plugin>
        <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-project-info-reports-plugin</artifactId>
          <version>2.9</version>
        </plugin>
        <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-surefire-plugin</artifactId>
          <version>2.19.1</version>
        </plugin>
        <plugin>
          <groupId>org.bsc.maven</groupId>
          <artifactId>maven-processor-plugin</artifactId>
          <version>3.2.0</version>
        </plugin>
        <plugin>
          <groupId>org.codehaus.mojo</groupId>
          <artifactId>cobertura-maven-plugin</artifactId>
          <version>2.7</version>
        </plugin>
        <plugin>
          <groupId>org.jacoco</groupId>
          <artifactId>jacoco-maven-plugin</artifactId>
          <version>0.7.8</version>
        </plugin>
      </plugins>
    </pluginManagement>
  </build>
</project>

some-entities

Persistence-Unit 에 해당하는 모듈이다. 중요한 점으로 이 묘듈은 persistence.xml 파일을 포함하지 않는다는 것이다. persistence.xml 파일은 최종 application 에서 정의한다.

javaee-api

우선적으로 필요한 의존성은 javaee-api이다. provided scope로 선언한다.

  ...
  <groupId>some</groupId>
  <artifactId>some-entities</artifactId>
  <version>0.1.0-SNAPSHOT</version>
  <packaging>jar</packaging>

  ...

  <dependencies>
    <dependency>
      <groupId>javax</groupId>
      <artifactId>javaee-api</artifactId>
      <scope>provided</scope>
    </dependency>
  </dependencies>

  ...

</project>

BaseEntity.java

필요에 따라서 다른 entity들이 상속할 수 있는 기본클래스를 선언할 수도 있다.

@MappedSuperclass
public abstract class BaseEntity implements Serializable {

    // ...

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = COLUMN_NAME_ID, nullable = false, updatable = false)
    @XmlAttribute
    private Long id;

    // ...
}

metamodel generation

JPA Metamodel 들을 생성한다. maven-processor-pluginhibernate-jpamodelgen을 사용한다.

  ...
  <build>
    ...
    <plugins>
      ...
      <plugin>
        <groupId>org.bsc.maven</groupId>
        <artifactId>maven-processor-plugin</artifactId>
        <executions>
          <execution>
            <id>generate-metamodels</id>
            <goals>
              <goal>process</goal>
            </goals>
            <phase>generate-sources</phase>
            <configuration>
              <processors>
                <processor>org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor</processor>
              </processors>
            </configuration>
          </execution>
        </executions>
        <dependencies>
          <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-jpamodelgen</artifactId>
            <version>5.2.6.Final</version>
          </dependency>
        </dependencies>
      </plugin>
      ...
    </plugins>
    ...
  </build>
  ...

src/test/resources/META-INF/persistence.xml

단위시험을 위해 in-memory database를 이용하는 persistence.xml 파일을 준비하자.

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.1"
             xmlns="http://xmlns.jcp.org/xml/ns/persistence"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd">
  <persistence-unit name="somePU" transaction-type="RESOURCE_LOCAL">
    <provider>${provider}</provider>
    <class>com.mycompany.some.Some</class>
    ...
    <validation-mode>CALLBACK</validation-mode>
    <properties>
      <property name="javax.persistence.jdbc.driver" value="${javax.persistence.jdbc.driver}"/>
      <property name="javax.persistence.jdbc.url" value="${javax.persistence.jdbc.url}"/>
      <property name="javax.persistence.schema-generation.database.action" value="create"/>
    </properties>
  </persistence-unit>
</persistence>

validation-modeCALLBACK으로 설정했으므로 validation framework을 추가해 줘야 한다.

    ...
    <dependency>
      <groupId>org.glassfish</groupId>
      <artifactId>javax.el</artifactId>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate-validator</artifactId>
      <scope>test</scope>
    </dependency>
    ...

자 이제 h2, derby 등의 database와 eclipselinke, hibernate-core등의 JPA provider 등을 선택적으로 사용할 수 있는 profile 들을 작성한다.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  ...
  <profiles>
    <profile>
      <id>derby</id>
      <activation>
        <activeByDefault>true</activeByDefault>
      </activation>
      <properties>
        <javax.persistence.jdbc.driver>org.apache.derby.jdbc.EmbeddedDriver</javax.persistence.jdbc.driver>
        <javax.persistence.jdbc.url>jdbc:derby:memory:someDB;create=true</javax.persistence.jdbc.url>
      </properties>
      <dependencies>
        <dependency>
          <groupId>org.apache.derby</groupId>
          <artifactId>derby</artifactId>
          <scope>test</scope>
        </dependency>
      </dependencies>
    </profile>
    <profile>
      <id>h2</id>
      <properties>
        <javax.persistence.jdbc.driver>org.h2.Driver</javax.persistence.jdbc.driver>
        <javax.persistence.jdbc.url>jdbc:h2:mem:someDB</javax.persistence.jdbc.url>
      </properties>
      <dependencies>
        <dependency>
          <groupId>com.h2database</groupId>
          <artifactId>h2</artifactId>
          <scope>test</scope>
        </dependency>
      </dependencies>
    </profile>
    <profile>
      <id>eclipselink</id>
      <activation>
        <activeByDefault>true</activeByDefault>
      </activation>
      <properties>
        <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
      </properties>
      <dependencies>
        <dependency>
          <groupId>org.eclipse.persistence</groupId>
          <artifactId>eclipselink</artifactId>
          <scope>test</scope>
        </dependency>
      </dependencies>
    </profile>
    <profile>
      <id>hibernate</id>
      <properties>
        <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
      </properties>
      <dependencies>
        <dependency>
          <groupId>org.hibernate</groupId>
          <artifactId>hibernate-core</artifactId>
          <scope>test</scope>
        </dependency>
      </dependencies>
    </profile>
  </profiles>
</project>

기본적으로 derbyeclipselinkactiveByDefault 로 설정하였다. 다음과 같이 profile을 바꿔가면서 시험할 수 있다.

$ mvn -Ph2,eclipselink test
$ mvn -Pderby,hibernate test

some-services

some-resources

RESTful HTTP DELETE?


DELETE method 의 응답으로 어떤 값을 사용해야 할까? 하고 갑자기 궁금하던 중, 같은 논점에 대해, 상당양의 검색결과를 얻었다.

일단 idempotence 에 대한 내용은 RFC 2616 / 9.1.2 Idempotent Methods 에서 알아볼 수 있다.

RFC 의 내용은 그리 논란거리가 아니지만 이게 REST(ful) 동네로 넘어오면서 의견이 분분해지는 것 같다.

debate

논점을 좀 간단하게 하자면, 204 만 써야 하는가 아니면 404/410 등도 같이 써야 한다는 것인데…

개인적으로 204 를 사용하는 것이 옳다고 본다. 4xx 을 써도 된다는 의견들이 잘못됐다는 것은 아니지만 4xx 을 사용해야 한는 경우는 그것보다 더 넓은 의미와 상황을 포함한다.

Overriding JTA into RESOUCE-LOCAL while Unit Testing


references

JTA

Let’s say you have a very simple persistence.xml looks like this.

<persistence-unit name="somePU" transaction-type="JTA">
  <jta-data-source>jdbc/someDS</jta-data-source>
  <exclude-unlisted-classes>false</exclude-unlisted-classes>
  <properties>
    <property name="javax.persistence.validation.mode" value="CALLBACK" />
  </properties>
</persistence-unit>

RESOUCE-LOCAL

You can, maybe you should, override it for unit testing. Following codes uses EclipseLink dependent API.

final Map<String, String> properties = new HashMap<>();
properties.put(PersistenceUnitProperties.TRANSACTION_TYPE,
               PersistenceUnitTransactionType.RESOURCE_LOCAL.name());
properties.put(PersistenceUnitProperties.JDBC_DRIVER,
               "org.apache.derby.jdbc.EmbeddedDriver");
properties.put(PersistenceUnitProperties.JDBC_URL,
               "jdbc:derby:memory:testDB;create=true");
properties.put(PersistenceUnitProperties.DDL_GENERATION,
               PersistenceUnitProperties.CREATE_ONLY);
properties.put(PersistenceUnitProperties.LOGGING_LEVEL,
               Level.FINE.getName());
properties.put(PersistenceUnitProperties.TARGET_SERVER,
               TargetServer.None);
ENTITY_MANAGER_FACTORY
    = createEntityManagerFactory("somePU", properties);